/*
Module : FTPTRANSFERDLG.CPP
Purpose: Defines the implementation for an MFC dialog which performs FTP uploads 
         and downloads similiar to the Internet Explorer download dialog
Created: PJN / 03-01-2000
History: PJN / 18-10-2000 1. Added a m_bPromptOverwrite variable 
                          2. Fixed a bug in the way the progress control was being updated
         PJN / 11-06-2001 1. Updated copyright details.
                          2. Now provides Bandwidth throtling support. Thanks to Karim Mribti for
                          this good addition.
                          3. Minor code tidy up thanks to BoundsChecker
                          4. m_bPromptForOverwrite now works correctly for downloads as well as uploads
                          5. Fixed a problem with the progress control not updating correctly
         PJN / 10-10-2001 1. Fixed a problem where old style MFC exception handling was being used
                          instead of C++ standard.
         PJN / 17-10-2001 1. Now includes support for PASV
                          2. Fixed a Unicode issue in CFTPTransferDlg::OnStatusCallBack
                          3. Support for precongfigured or direct settings.
                          4. Support for proxies
         PJN / 02-02-2002 1. Now includes support for FTP resumes via the FTP command "REST". Please
                          note that IE 5 is required to support this. This support is experimental,
                          so your feedback is welcome.
                          2. Updated documentation to include explicit requirements for the class.
         PJN / 20-04-2002 1. Modified the code to correctly support FTP resumes. Feeedback from 
                          jony for reporting this issue.
                          2. Fixed a bug in the call to AfxBeginThread
         PJN / 05-06-2002 1. Renamed some string resources used by the class to maintain naming 
                          consistency.
         PJN / 30-10-2002 1. Fixed a problem with seeking to the correct position when resuming a transfer. 
                          Thanks to Liping Dai for this bug report.
         PJN / 13-06-2003 1. Now supports connection timeouts since wininet doesnt <ggg>.
                          2. Other various tidy ups to the code, such as review of all the TRACE statements
                          and calls to GetLastError


Copyright (c) 2000 - 2003 by PJ Naughter.  (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 
when your product is released in binary form. You are allowed to modify the source code in any way you want 
except you cannot modify the copyright details at the top of each module. If you want to distribute source 
code with your application, then you are only allowed to distribute versions released by the author. This is 
to maintain a single distribution point for the source code. 

All rights reserved.

*/


/////////////////////////////////  Includes  //////////////////////////////////
#include "stdafx.h"
#include "resource.h"
#include "FTPTransferDlg.h"



///////////////////////////////// Defines /////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

const UINT WM_FTPTRANSFER_THREAD_FINISHED = WM_APP + 1;
const UINT WM_FTPTRANSFER_ASK_OVERWRITE_FILE = WM_APP + 2;



///////////////////////////////// Implementation //////////////////////////////


//Class which handles dynamically calling function which must be constructed at run time
//since support for resuming FTP requires IE5 to be installed, which we do not
//want to limit the code to. To avoid the loader bringing up a message such as "Failed to 
//load due to missing export...", the function is constructed using GetProcAddress. The 
//CFTPTransferDlg function then checks to see if these function pointer is NULL and if it 
//returns FALSE and sets the last error to ERROR_CALL_NOT_IMPLEMENTED and fails the call

class _FTP_DOWNLOAD_DATA
{
public:
//Constructors /Destructors
  _FTP_DOWNLOAD_DATA();
  ~_FTP_DOWNLOAD_DATA();

  HINSTANCE m_hWininet;

  typedef BOOL (__stdcall FTPCOMMAND)(HINTERNET, BOOL, DWORD, LPCTSTR, DWORD, HINTERNET*);
  typedef FTPCOMMAND* LPFTPCOMMAND;

  LPFTPCOMMAND m_lpfnFtpCommand;
};

_FTP_DOWNLOAD_DATA::_FTP_DOWNLOAD_DATA()
{
  m_hWininet = GetModuleHandle(_T("WININET.DLL"));
  if (m_hWininet)
  {
#ifdef _UNICODE
    m_lpfnFtpCommand = (LPFTPCOMMAND) GetProcAddress(m_hWininet, "FtpCommandW");
#else
    m_lpfnFtpCommand = (LPFTPCOMMAND) GetProcAddress(m_hWininet, "FtpCommandA");
#endif
  }
}

_FTP_DOWNLOAD_DATA::~_FTP_DOWNLOAD_DATA()
{
  m_hWininet = NULL;
}

_FTP_DOWNLOAD_DATA _FtpDownloadData;



IMPLEMENT_DYNAMIC(CFTPTransferDlg, CDialog);


CFTPTransferDlg::CFTPTransferDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CFTPTransferDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CFTPTransferDlg)
	//}}AFX_DATA_INIT
  m_hInternetSession = NULL;
  m_hFTPConnection = NULL;
  m_hFTPFile = NULL;
  m_bAbort = FALSE;
  m_bSafeToClose = FALSE;
  m_pThread = NULL;
  m_bUsingAttached = NULL;
  m_bDownload = TRUE;
  m_nPort = INTERNET_DEFAULT_FTP_PORT;
  m_bBinary = TRUE;
  m_bPromptOverwrite = TRUE;
  m_dbLimit = 0.0f;
  m_bPasv = FALSE;
  m_bUsePreconfig = TRUE;
  m_bUseProxy = FALSE;
  m_dwStartPos = 0;
  m_dwConnectionTimeout = 15000;
  m_dwTempError = ERROR_SUCCESS;
  m_pszReadBuf = NULL; 
  m_pdwBytesToRead = NULL;
  m_pdwBytesRead = NULL;
}

void CFTPTransferDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CFTPTransferDlg)
	DDX_Control(pDX, IDC_STATUS, m_ctrlStatus);
	DDX_Control(pDX, IDC_TRANSFER_RATE, m_ctrlTransferRate);
	DDX_Control(pDX, IDC_TIMELEFT, m_ctrlTimeLeft);
	DDX_Control(pDX, IDC_PROGRESS1, m_ctrlProgress);
	DDX_Control(pDX, IDC_FILESTATUS, m_ctrlFileStatus);
	DDX_Control(pDX, IDC_ANIMATE1, m_ctrlAnimate);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CFTPTransferDlg, CDialog)
	//{{AFX_MSG_MAP(CFTPTransferDlg)
	ON_WM_DESTROY()
	ON_WM_CLOSE()
	//}}AFX_MSG_MAP
  ON_MESSAGE(WM_FTPTRANSFER_THREAD_FINISHED, OnThreadFinished)
  ON_MESSAGE(WM_FTPTRANSFER_ASK_OVERWRITE_FILE, OnAskOverwrite)
END_MESSAGE_MAP()

void CFTPTransferDlg::AttachToExisitingHandles(HINTERNET hInternetSession, HINTERNET hFTPConnection)
{
  ASSERT(m_hInternetSession == NULL);
  ASSERT(m_hFTPConnection == NULL);
  m_hInternetSession = hInternetSession;
  m_hFTPConnection = hFTPConnection;
  m_bUsingAttached = TRUE;
}

LRESULT CFTPTransferDlg::OnAskOverwrite(WPARAM /*wParam*/, LPARAM /*lParam*/)
{
  CString sMsg;
  AfxFormatString1(sMsg, IDS_FTPTRANSFER_OK_TO_OVERWRITE, m_sRemoteFile);
  return (AfxMessageBox(sMsg, MB_YESNO) == IDYES);
}

LRESULT CFTPTransferDlg::OnThreadFinished(WPARAM wParam, LPARAM /*lParam*/)
{
  //It's now safe to close since the thread has signaled us
  m_bSafeToClose = TRUE;

  //Stop the animation
  m_ctrlAnimate.Stop();

  //If an error occured display the message box
  if (m_bAbort)
    EndDialog(IDCANCEL);
  else if (wParam)
  {
    if (m_sError.GetLength())  
      AfxMessageBox(m_sError);
    EndDialog(IDCANCEL);
  }
  else
    EndDialog(IDOK);

  return 0L;
}

BOOL CFTPTransferDlg::OnInitDialog() 
{
  //Let the parent class do its thing
	CDialog::OnInitDialog();

  //Change the caption of the dialog if necessary
  if (!m_bDownload)
  {
    CString sCaption;
    sCaption.LoadString(IDS_FTPTRANSFER_UPLOAD_CAPTION);
    SetWindowText(sCaption);
  }

  //Set the range of the progress control
  m_ctrlProgress.SetRange(0, 100);

  //Setup the animation control
  m_ctrlAnimate.Open(IDR_FTPTRANSFER_ANIMATION);

  //Check to see if the file we are downloading exists and if
  //it does, then ask the user if they were it overwritten
  ASSERT(m_sLocalFile.GetLength());
  CFileStatus fs;
  BOOL bDownloadFileExists = CFile::GetStatus(m_sLocalFile, fs);
  if (m_bDownload && bDownloadFileExists && m_bPromptOverwrite)
  {
    CString sMsg;
    AfxFormatString1(sMsg, IDS_FTPTRANSFER_OK_TO_OVERWRITE, m_sLocalFile);
    if (AfxMessageBox(sMsg, MB_YESNO) != IDYES)
    {
      TRACE(_T("CFTPTransferDlg::OnInitDialog, Failed to confirm file overwrite, download aborted\n"));
      EndDialog(IDCANCEL);
      return TRUE;
    }
  }

  //Try and open the file we will downloading into / uploading from
  if (m_bDownload)
  {
    DWORD dwFileFlags = 0;
    if (bDownloadFileExists)
      dwFileFlags = CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::shareDenyWrite;
    else
      dwFileFlags = CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite;

    CFileException ex;
    if (!m_LocalFile.Open(m_sLocalFile, dwFileFlags, &ex))
    {
      TRACE(_T("CFTPTransferDlg::OnInitDialog, Failed to open the file to download into, Error:%d\n"), ex.m_lOsError);
      CString sError;
      sError.Format(_T("%d"), ex.m_lOsError);
      CString sMsg;
      AfxFormatString1(sMsg, IDS_FTPTRANSFER_FAIL_LOCALFILE_OPEN1, sError);
      AfxMessageBox(sMsg);
      EndDialog(IDCANCEL);
      return TRUE;
    }

		//Seek to the end of the file
    try
	  {
      m_LocalFile.Seek(m_dwStartPos, CFile::begin); 
      m_LocalFile.SetLength(m_dwStartPos);
	  }
    catch(CFileException* pEx)                                         
    {
      TRACE(_T("CFTPTransferDlg::OnInitDialog, An exception occured while setting file pointer position, Error:%d\n"), pEx->m_lOsError);
      CString sError;
      sError.Format(_T("%d"), pEx->m_lOsError);
      CString sMsg;
      AfxFormatString1(sMsg, IDS_FTPTRANSFER_FAIL_FILE_SEEK, sError);
		  pEx->Delete();
      AfxMessageBox(sMsg);
		  EndDialog(IDCANCEL);
      return TRUE;
    }	
  }
  else
  {
    CFileException ex;
    if (!m_LocalFile.Open(m_sLocalFile, CFile::modeRead | CFile::shareDenyWrite, &ex))
    {
      TRACE(_T("CFTPTransferDlg::OnInitDialog, Failed to open the file to upload, Error:%d\n"), ex.m_lOsError);
      CString sError;
      sError.Format(_T("%d"), ex.m_lOsError);
      CString sMsg;
      AfxFormatString1(sMsg, IDS_FTPTRANSFER_FAIL_LOCALFILE_OPEN2, sError);
      AfxMessageBox(sMsg);
      EndDialog(IDCANCEL);
      return TRUE;
    }
  }

	//Set the file status text
  CString sFileStatus;
  ASSERT(m_sRemoteFile.GetLength());
  ASSERT(m_sServer.GetLength());
  if (m_bDownload)
    AfxFormatString2(sFileStatus, IDS_FTPTRANSFER_FILESTATUS1, m_sRemoteFile, m_sServer);
  else
    AfxFormatString2(sFileStatus, IDS_FTPTRANSFER_FILESTATUS2, m_sRemoteFile, m_sServer);
  m_ctrlFileStatus.SetWindowText(sFileStatus);

  //Spin off the background thread which will do the actual file transfer
  m_pThread = AfxBeginThread(_TransferThread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
  if (m_pThread == NULL)
  {
    TRACE(_T("CFTPTransferDlg::OnInitDialog, Failed to create transfer thread, dialog is aborting\n"));
    EndDialog(IDCANCEL);
    return TRUE;
  }
  m_pThread->m_bAutoDelete = FALSE;
  m_pThread->ResumeThread();
	
	return TRUE;
}

UINT CFTPTransferDlg::_TransferThread(LPVOID pParam)
{
  //Convert from the SDK world to the C++ world
  CFTPTransferDlg* pDlg = (CFTPTransferDlg*) pParam;
  ASSERT(pDlg);
  ASSERT(pDlg->IsKindOf(RUNTIME_CLASS(CFTPTransferDlg)));
  pDlg->TransferThread();
  return 0;
}

void CFTPTransferDlg::SetPercentage(int nPercentage)
{
  //Change the progress control
  m_ctrlProgress.SetPos(nPercentage);

  //Change the caption text
  CString sPercentage;
  sPercentage.Format(_T("%d"), nPercentage);
  CString sCaption;
  AfxFormatString2(sCaption, IDS_FTPTRANSFER_PERCENTAGE, sPercentage, m_sRemoteFile);
  SetWindowText(sCaption);
}

void CFTPTransferDlg::SetTimeLeft(DWORD dwSecondsLeft, DWORD dwBytesRead, DWORD dwFileSize)
{
  CString sCopied;
  if (dwBytesRead < 1024)
  {
    CString sBytes;
    sBytes.Format(_T("%d"), dwBytesRead);
    AfxFormatString1(sCopied, IDS_FTPTRANSFER_BYTES, sBytes);
  }
  else if (dwBytesRead < 1048576)
  {
    CString sKiloBytes;
    sKiloBytes.Format(_T("%0.1f"), dwBytesRead/1024.0);
    AfxFormatString1(sCopied, IDS_FTPTRANSFER_KILOBYTES, sKiloBytes);
  }
  else
  {
    CString sMegaBytes;
    sMegaBytes.Format(_T("%0.2f"), dwBytesRead/1048576.0);
    AfxFormatString1(sCopied, IDS_FTPTRANSFER_MEGABYTES, sMegaBytes);
  }

  CString sTotal;
  if (dwFileSize < 1024)
  {
    CString sBytes;
    sBytes.Format(_T("%d"), dwFileSize);
    AfxFormatString1(sTotal, IDS_FTPTRANSFER_BYTES, sBytes);
  }
  else if (dwFileSize < 1048576)
  {
    CString sKiloBytes;
    sKiloBytes.Format(_T("%0.1f"), dwFileSize/1024.0);
    AfxFormatString1(sTotal, IDS_FTPTRANSFER_KILOBYTES, sKiloBytes);
  }
  else
  {
    CString sMegaBytes;
    sMegaBytes.Format(_T("%0.2f"), dwFileSize/1048576.0);
    AfxFormatString1(sTotal, IDS_FTPTRANSFER_MEGABYTES, sMegaBytes);
  }

  CString sOf;
  AfxFormatString2(sOf, IDS_FTPTRANSFER_OF, sCopied, sTotal);

  CString sTime;
  if (dwSecondsLeft < 60)
  {
    CString sSeconds;
    sSeconds.Format(_T("%d"), dwSecondsLeft);
    AfxFormatString1(sTime, IDS_FTPTRANSFER_SECONDS, sSeconds);
  }
  else
  {
    DWORD dwMinutes = dwSecondsLeft / 60;
    DWORD dwSeconds = dwSecondsLeft % 60;
    CString sSeconds;
    sSeconds.Format(_T("%d"), dwSeconds);
    CString sMinutes;
    sMinutes.Format(_T("%d"), dwMinutes);
    if (dwSeconds == 0)
      AfxFormatString1(sTime, IDS_FTPTRANSFER_MINUTES, sMinutes);
    else
      AfxFormatString2(sTime, IDS_FTPTRANSFER_MINUTES_AND_SECONDS, sMinutes, sSeconds);
  }

  CString sTimeLeft;
  AfxFormatString2(sTimeLeft, IDS_FTPTRANSFER_TIMELEFT, sTime, sOf);
  m_ctrlTimeLeft.SetWindowText(sTimeLeft);
}

void CFTPTransferDlg::SetStatus(const CString& sCaption)
{
  m_ctrlStatus.SetWindowText(sCaption);
}

void CFTPTransferDlg::SetStatus(UINT nID)
{
  CString sCaption;
  sCaption.LoadString(nID);
  SetStatus(sCaption);
}

void CFTPTransferDlg::SetStatus(UINT nID, const CString& lpsz1)
{
  CString sStatus;
  AfxFormatString1(sStatus, nID, lpsz1);
  SetStatus(sStatus);
}

void CFTPTransferDlg::SetTransferRate(double KbPerSecond)
{
  CString sRate;
  if (KbPerSecond < 1)
  {
    CString sBytesPerSecond;
    sBytesPerSecond.Format(_T("%0.0f"), KbPerSecond*1024);
    AfxFormatString1(sRate, IDS_FTPTRANSFER_BYTESPERSECOND, sBytesPerSecond);
  }
  else if (KbPerSecond < 10)
  {
    CString sKiloBytesPerSecond;
    sKiloBytesPerSecond.Format(_T("%0.2f"), KbPerSecond);
    AfxFormatString1(sRate, IDS_FTPTRANSFER_KILOBYTESPERSECOND, sKiloBytesPerSecond);
  }
  else
  {
    CString sKiloBytesPerSecond;
    sKiloBytesPerSecond.Format(_T("%0.0f"), KbPerSecond);
    AfxFormatString1(sRate, IDS_FTPTRANSFER_KILOBYTESPERSECOND, sKiloBytesPerSecond);
  }
  m_ctrlTransferRate.SetWindowText(sRate);
}

void CFTPTransferDlg::PlayAnimation()
{
  m_ctrlAnimate.Play(0, (UINT)-1, (UINT)-1);
}

void CFTPTransferDlg::HandleThreadError(const CString& sError)
{
  //hive away the string
  m_sError = sError;

  //Delete the file being downloaded to if it is present
  if (m_bDownload)
  {
    m_LocalFile.Close();
    ::DeleteFile(m_sLocalFile);
  }

  PostMessage(WM_FTPTRANSFER_THREAD_FINISHED, 1);
}

void CFTPTransferDlg::HandleThreadErrorWithLastError(UINT nIDError, DWORD dwLastError)
{
  //Form the error string to report
  CString sError;

  DWORD dwErr = dwLastError;
  if (dwErr == 0)
    dwErr = ::GetLastError();
  if (dwErr == ERROR_INTERNET_EXTENDED_ERROR)
  {
    DWORD dwInetError;
    DWORD dwSize=0;
    ::InternetGetLastResponseInfo(&dwInetError, NULL, &dwSize);
    TCHAR* pszResponse = new TCHAR[dwSize+1];
    ::InternetGetLastResponseInfo(&dwInetError, pszResponse, &dwSize);
    pszResponse[dwSize] = _T('\0');
    sError = pszResponse;
    sError = _T("\n") + sError; //Add a line feed between the normal message
                                //and the verbose error message
    delete [] pszResponse;
  }
  else
    sError.Format(_T("%d"), dwErr);

  //pass the buck to the other version
  CString sCompleteString;
  AfxFormatString1(sCompleteString, nIDError, sError);
  HandleThreadError(sCompleteString);
}

void CFTPTransferDlg::HandleThreadError(UINT nIDError)
{
  m_sError.LoadString(nIDError);
  PostMessage(WM_FTPTRANSFER_THREAD_FINISHED, 1);
}

BOOL CFTPTransferDlg::ResumeTransfer(CString& sError)
{
  //Check that the FtpCommand function is available
  if (_FtpDownloadData.m_lpfnFtpCommand == NULL)
  {
    sError.LoadString(IDS_FTPTRANSFER_COMMAND_MISSING);
    TRACE(_T("CFTPTransferDlg::ResumeTransfer, FtpCommand method not available in Wininet dll, FTP resumes are not supported\n"));
    return FALSE;
  }

  //Form the resume request
  CString sRequest;
  sRequest.Format(_T("REST %d"), m_dwStartPos);

  //Send the resume request
  BOOL bSuccess = _FtpDownloadData.m_lpfnFtpCommand(m_hFTPConnection, FALSE, FTP_TRANSFER_TYPE_BINARY, sRequest, 0, NULL); 
  if (!bSuccess)
  {
    DWORD dwLastError = ::GetLastError();
    TRACE(_T("CFTPTransferDlg::ResumeTransfer, Failed in call to FtpCommand, Error:%d\n"), dwLastError);

    //Form the error string
    CString sNumericError;
    sNumericError.Format(_T("%d"), dwLastError);
    AfxFormatString1(sError, IDS_FTPTRANSFER_COMMAND_ERROR, sNumericError);
  }
  else
  {
    //Check the reponse to see if we get a "350" response code
    DWORD dwInetError;
    DWORD dwSize=0;
    ::InternetGetLastResponseInfo(&dwInetError, NULL, &dwSize);
    TCHAR* pszResponse = new TCHAR[dwSize+1];
    ::InternetGetLastResponseInfo(&dwInetError, pszResponse, &dwSize);
    pszResponse[dwSize] = _T('\0');

    //Check to see if there is a 350 response code in the extended error text anywhere
    bSuccess = (_ttoi(pszResponse) == 350);

    //Tidy up the heap memory now that we have finished with it
    delete [] pszResponse;

    if (!bSuccess)
    {
      //Form the error string
      CString sResponse(pszResponse);
      TRACE(_T("CFTPTransferDlg::ResumeTransfer, Resume not supported, Response: %s\n"), sResponse);
      AfxFormatString1(sError, IDS_FTPTRANSFER_RESUME_NOT_SUPPORTED, sResponse);
    }
  }
  
  return bSuccess;
}

UINT CFTPTransferDlg::_MakeConnectionThread(LPVOID pParam)
{
  //Get back the object pointer
  CFTPTransferDlg* pThis = (CFTPTransferDlg*) pParam;
  ASSERT(pThis);

  ASSERT(pThis->m_hFTPConnection == NULL);
  ASSERT(pThis->m_sServer.GetLength());
  if (pThis->m_sUserName.GetLength())
    pThis->m_hFTPConnection = ::InternetConnect(pThis->m_hInternetSession, pThis->m_sServer, pThis->m_nPort, pThis->m_sUserName, 
                                                pThis->m_sPassword, INTERNET_SERVICE_FTP, pThis->m_bPasv ? INTERNET_FLAG_PASSIVE : 0, (DWORD) pThis);
  else
    pThis->m_hFTPConnection = ::InternetConnect(pThis->m_hInternetSession, pThis->m_sServer, pThis->m_nPort, NULL, 
                                                NULL, INTERNET_SERVICE_FTP, pThis->m_bPasv ? INTERNET_FLAG_PASSIVE : 0, (DWORD) pThis);
  if (pThis->m_hFTPConnection == NULL)
  {
    //Hive away this threads error code as we have use for it in the main thread
    pThis->m_dwTempError = ::GetLastError();
  }

  return pThis->m_hFTPConnection ? 0 : 1;
}

UINT CFTPTransferDlg::_ReadDataThread(LPVOID pParam)
{
  //Get back the object pointer
  CFTPTransferDlg* pThis = (CFTPTransferDlg*) pParam;
  ASSERT(pThis);

  ASSERT(pThis->m_hFTPFile == NULL);
  ASSERT(pThis->m_pszReadBuf);
  ASSERT(pThis->m_pdwBytesToRead);
  ASSERT(pThis->m_pdwBytesRead);

  HANDLE handles[2];
  handles[0] = pThis->m_StopReadThreadEvent;
  handles[1] = pThis->m_ToReadThreadEvent;
  BOOL bWantStop = FALSE;
  while (!bWantStop)
  {
    //Wait for a request to exit the thread or to read some data
    DWORD dwWait = WaitForMultipleObjects(2, handles, FALSE, INFINITE);

    int nSignaledHandle = dwWait - WAIT_OBJECT_0;
    if (nSignaledHandle == 0) //It was the stop request
      bWantStop = TRUE;
    else if (nSignaledHandle == 1) //It was the read request
    {
      if (!::InternetReadFile(pThis->m_hFTPFile, pThis->m_pszReadBuf, *pThis->m_pdwBytesToRead, pThis->m_pdwBytesRead))
      {
        //Hive away this threads error code as we have use for it in the main thread
        pThis->m_dwTempError = ::GetLastError();
      }
      else
      {
        //signal the read event so that the main thread knows that InternetReadFile has finished
        pThis->m_HaveReadThreadEvent.SetEvent();
      }
    }
  }

  return 0;
}

void CFTPTransferDlg::TransferThread()
{
  //Create the Internet session handle (if needed)
  if (!m_bUsingAttached)
  {
    ASSERT(m_hInternetSession == NULL);

    if (m_bUsePreconfig)
      m_hInternetSession = ::InternetOpen(AfxGetAppName(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    else if (m_bUseProxy)
      m_hInternetSession = ::InternetOpen(AfxGetAppName(), INTERNET_OPEN_TYPE_PROXY, m_sProxy, NULL, 0);
    else
      m_hInternetSession = ::InternetOpen(AfxGetAppName(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

    if (m_hInternetSession == NULL)
    {
      DWORD dwLastError = ::GetLastError();
      TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetOpen, Error:%d\n"), dwLastError);
      HandleThreadErrorWithLastError(IDS_FTPTRANSFER_GENERIC_ERROR, dwLastError);
      return;
    }

    //Should we exit the thread
    if (m_bAbort)
    {
      PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
      return;
    } 
  } 
  ASSERT(m_hInternetSession);  

  //Setup the status callback function on the Internet session handle
  INTERNET_STATUS_CALLBACK pOldCallback = ::InternetSetStatusCallback(m_hInternetSession, _OnStatusCallBack);
  if (pOldCallback == INTERNET_INVALID_STATUS_CALLBACK)
  {
    DWORD dwLastError = ::GetLastError();
    TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetSetStatusCallback, Error:%d\n"), dwLastError);
    HandleThreadErrorWithLastError(IDS_FTPTRANSFER_GENERIC_ERROR, dwLastError);
    return;
  }

  //Should we exit the thread
  if (m_bAbort)
  {
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
    return;
  }  

  //Make the connection to the FTP server (if needed)
  if (!m_bUsingAttached)
  {
    //Spin of the thread which does the connection for us
    CWinThread* pConnectionThread = AfxBeginThread(_MakeConnectionThread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
    if (pConnectionThread == NULL)
    {
      DWORD dwLastError = GetLastError();

      TRACE(_T("CFTPTransferDlg::TransferThread, Failed in create the connection thread, Error:%d\n"), dwLastError);
      if (pOldCallback)
        ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
      HandleThreadErrorWithLastError(IDS_FTPTRANSFER_FAIL_CREATE_CONNECTION_THREAD, dwLastError);
      return;
    }

    //We're looking after its memory
    pConnectionThread->m_bAutoDelete = FALSE;

    //Everything ok, Resume the thread
    pConnectionThread->ResumeThread();

    //Wait for the thread to return or a timeout occurs
    if (WaitForSingleObject(pConnectionThread->m_hThread, m_dwConnectionTimeout) == WAIT_TIMEOUT)
    {
      //Close the wininet session handle (which will cause the worker thread to return from its blocking call which 
      //most likely will be the call to InternetConnect)
      ::InternetCloseHandle(m_hInternetSession);
      m_hInternetSession = NULL;
    
      //Wait until the worker thread exits, should be quick since we have just closed the session handle above!
      WaitForSingleObject(pConnectionThread->m_hThread, INFINITE);
      delete pConnectionThread;

      TRACE(_T("CFTPTransferDlg::TransferThread, Failed in connect to the FTP server in a timely manner\n"));
      if (pOldCallback)
        ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
      HandleThreadErrorWithLastError(IDS_FTPTRANSFER_FAIL_CONNECT_SERVER, ERROR_TIMEOUT);
      return;
    }

    //If we got here
    delete pConnectionThread;

    //Check to see if the connection was successful
    if (m_hFTPConnection == NULL)
    {
      TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetConnect, Error:%d\n"), m_dwTempError);
      if (pOldCallback)
        ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
      HandleThreadErrorWithLastError(IDS_FTPTRANSFER_FAIL_CONNECT_SERVER, m_dwTempError);
      return;
    }

    //Should we exit the thread
    if (m_bAbort)
    {
      if (pOldCallback)
        ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
      PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
      return;
    } 
  }
  ASSERT(m_hFTPConnection); 

  //Start the animation to signify that the download is taking place
  PlayAnimation();

  //Update the status control to reflect that we are getting the file information
  SetStatus(IDS_FTPTRANSFER_GETTING_FILE_INFORMATION);

  // Get the length of the file to transfer            
  DWORD dwFileSize = 0;
  BOOL bGotFileSize = FALSE;
  if (m_bDownload)
  {
    WIN32_FIND_DATA wfd;
    HINTERNET hFind = ::FtpFindFirstFile(m_hFTPConnection, m_sRemoteFile, &wfd, INTERNET_FLAG_RELOAD | 
                                         INTERNET_FLAG_DONT_CACHE, (DWORD) this); 
    if (hFind)
    {
      //Set the progress control range
      bGotFileSize = TRUE;
      dwFileSize = (DWORD) wfd.nFileSizeLow;

      //Destroy the enumeration handle now that we are finished with it
      InternetCloseHandle(hFind);
    }
  }
  else
  {
    bGotFileSize = TRUE;
    dwFileSize = m_LocalFile.GetLength();
  }

  //Should we exit the thread
  if (m_bAbort)
  {
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
    return;
  }  

  //check to see if the file already exists on the server  
  if (!m_bDownload && m_bPromptOverwrite)
  {
    WIN32_FIND_DATA wfd;
    HINTERNET hFind = ::FtpFindFirstFile(m_hFTPConnection, m_sRemoteFile, &wfd, INTERNET_FLAG_RELOAD | 
                                         INTERNET_FLAG_DONT_CACHE, (DWORD) this); 
    BOOL bFound = FALSE;
    if (hFind)
    {
      bFound = TRUE;
      
      //Destroy the enumeration handle now that we are finished with it
      InternetCloseHandle(hFind);
    }

    if (bFound && SendMessage(WM_FTPTRANSFER_ASK_OVERWRITE_FILE) == 0)  
    {
      if (pOldCallback)
        ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
      PostMessage(WM_FTPTRANSFER_THREAD_FINISHED, 1);
      return;
    }
  }

  //Should we exit the thread
  if (m_bAbort)
  {
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
    return;
  }  

  //Resume the transfer if requested to do so
  CString sError;
  if (m_dwStartPos && !ResumeTransfer(sError))
  {
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    HandleThreadError(sError);
    return;
  }

  //Open the remote file
  ASSERT(m_hFTPFile == NULL);
  if (m_bDownload)
  {
    if (m_bBinary)
      m_hFTPFile = FtpOpenFile(m_hFTPConnection, m_sRemoteFile, GENERIC_READ, FTP_TRANSFER_TYPE_BINARY | 
                               INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, (DWORD) this);
    else
      m_hFTPFile = FtpOpenFile(m_hFTPConnection, m_sRemoteFile, GENERIC_READ, FTP_TRANSFER_TYPE_ASCII | 
                               INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, (DWORD) this);
  }
  else
  {
    if (m_bBinary)  
      m_hFTPFile = FtpOpenFile(m_hFTPConnection, m_sRemoteFile, GENERIC_WRITE, FTP_TRANSFER_TYPE_BINARY | 
                               INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, (DWORD) this);
    else
      m_hFTPFile = FtpOpenFile(m_hFTPConnection, m_sRemoteFile, GENERIC_WRITE, FTP_TRANSFER_TYPE_ASCII | 
                               INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, (DWORD) this);
  }
  if (m_hFTPFile == NULL)
  {
    DWORD dwLastError = ::GetLastError();
    TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to FtpOpenFile, Error:%d\n"), dwLastError);
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    HandleThreadErrorWithLastError(IDS_FTPTRANSFER_FAIL_OPEN_FILE, dwLastError);
    return;
  }

  //Should we exit the thread
  if (m_bAbort)
  {
    if (pOldCallback)
      ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
    PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
    return;
  }  

  //Update the status to say that we are now uploading / downloading the file
  if (m_bDownload)
    SetStatus(IDS_FTPTRANSFER_RETREIVEING_FILE);
  else
    SetStatus(IDS_FTPTRANSFER_UPLOADING_FILE);

  //Now do the actual reading / writing of the file
  DWORD dwStartTicks = ::GetTickCount();
  DWORD dwCurrentTicks = dwStartTicks;
  DWORD dwBytesRead = 0;
  DWORD dwBytesWritten = 0;
  char szReadBuf[1024];
  DWORD dwBytesToRead = 1024;
  DWORD dwTotalBytesRead = 0;
  DWORD dwTotalBytesWritten = 0;  
  DWORD dwLastTotalBytes = 0;
  DWORD dwLastPercentage = 0;
  double q = 0.0f;
  m_pszReadBuf = szReadBuf;
  m_pdwBytesToRead = &dwBytesToRead;
  m_pdwBytesRead = &dwBytesRead;

  do
  {
    if (m_bDownload)
    {
      //Read from the remote file
      if (!::InternetReadFile(m_hFTPFile, szReadBuf, dwBytesToRead, &dwBytesRead))
      {
        DWORD dwLastError = ::GetLastError();
        TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetReadFile, Error:%d\n"), dwLastError);
        if (pOldCallback)
          ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
        HandleThreadErrorWithLastError(IDS_FTPTRANSFER_ERROR_READFILE, dwLastError);
        return;
      }
      else if (dwBytesRead && !m_bAbort)
      {
        //Write the data to file
        try
        {
          m_LocalFile.Write(szReadBuf, dwBytesRead);
        }
        catch(CFileException* pEx)
        {
          TRACE(_T("CFTPTransferDlg::TransferThread, An exception occured while writing to the download file, Error:%d\n"), pEx->m_lOsError);
          if (pOldCallback)
            ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
          HandleThreadErrorWithLastError(IDS_FTPTRANSFER_ERROR_READFILE, pEx->m_lOsError);
          pEx->Delete();
          return;
        }

        // For bandwidth throtling
		    if (m_dbLimit > 0.0f) 
        {
		      double t = (double)(GetTickCount() - dwStartTicks);
		      q = (double)((double)dwTotalBytesRead / t);
		     
		      if (q > m_dbLimit)	 
  			    Sleep((DWORD)((((q*t)/m_dbLimit)-t)));
		    }

        //Increment the total number of bytes read
        dwTotalBytesRead += dwBytesRead;  

        UpdateControlsDuringTransfer(dwStartTicks, dwCurrentTicks, dwTotalBytesRead, dwLastTotalBytes, 
                                     dwLastPercentage, bGotFileSize, dwFileSize);
      }
    }
    else
    {
      //Read the data from the local file
      try
      {
        dwBytesRead = m_LocalFile.Read(szReadBuf, 1024);
      }
      catch(CFileException* pEx)
      {
        TRACE(_T("CFTPTransferDlg::TransferThread, An exception occured while reading the local file, Error:%d\n"), pEx->m_lOsError);
        if (pOldCallback)
          ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
        HandleThreadErrorWithLastError(IDS_FTPTRANSFER_ERROR_READFILE, pEx->m_lOsError);
        pEx->Delete();
        return;
      }

      //Write to the remote file
      if (dwBytesRead)
      {
        if (!::InternetWriteFile(m_hFTPFile, szReadBuf, dwBytesRead, &dwBytesWritten))
        {
          DWORD dwLastError = ::GetLastError();
          TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetWriteFile, Error:%d\n"), dwLastError);
          if (pOldCallback)
            ::InternetSetStatusCallback(m_hInternetSession, pOldCallback);
          HandleThreadErrorWithLastError(IDS_FTPTRANSFER_ERROR_WRITEFILE, dwLastError);
          return;
        }
        else if (dwBytesWritten && !m_bAbort)
        {
          //Increment the total number of bytes read
          dwTotalBytesWritten += dwBytesWritten;  

          UpdateControlsDuringTransfer(dwStartTicks, dwCurrentTicks, dwTotalBytesWritten, dwLastTotalBytes, 
                                       dwLastPercentage, bGotFileSize, dwFileSize);
        }

        // For bandwidth throtling
		    if (m_dbLimit > 0.0f) 
        {
		      double t = (double)(GetTickCount() - dwStartTicks);
		      q = (double)((double)dwTotalBytesWritten / t);
		     
		      if (q > m_dbLimit)	 
  			    Sleep((DWORD)((((q*t)/m_dbLimit)-t)));
		    }
      }
    }
  } 
  while (dwBytesRead && !m_bAbort);

  //Delete the file being downloaded, if it is present and the download was aborted
  m_LocalFile.Close();
  if (m_bAbort && m_bDownload)
    ::DeleteFile(m_sLocalFile);

  //We're finished
  PostMessage(WM_FTPTRANSFER_THREAD_FINISHED);
}

void CFTPTransferDlg::UpdateControlsDuringTransfer(DWORD dwStartTicks, DWORD& dwCurrentTicks, DWORD dwTotalBytesRead, DWORD& dwLastTotalBytes, 
                                                   DWORD& dwLastPercentage, BOOL bGotFileSize, DWORD dwFileSize)
{
  if (bGotFileSize)
  {
    //Update the percentage downloaded in the caption
    DWORD dwPercentage = (DWORD) (dwTotalBytesRead * 100.0 / dwFileSize);
    if (dwPercentage != dwLastPercentage)
    {
      //Update the progress control bar
      SetPercentage(dwPercentage);
      dwLastPercentage = dwPercentage;
    }
  }

  //Update the transfer rate amd estimated time left every second
  DWORD dwNowTicks = GetTickCount();
  DWORD dwTimeTaken = dwNowTicks - dwCurrentTicks;
  if (dwTimeTaken > 1000)
  {
    double KbPerSecond = ((double)(dwTotalBytesRead) - (double)(dwLastTotalBytes)) / ((double)(dwTimeTaken));
    SetTransferRate(KbPerSecond);

    //Setup for the next time around the loop
    dwCurrentTicks = dwNowTicks;
    dwLastTotalBytes = dwTotalBytesRead;

    if (bGotFileSize)
    {
      //Update the estimated time left
      if (dwTotalBytesRead)
      {
        DWORD dwSecondsLeft = (DWORD) (((double)dwNowTicks - dwStartTicks) / dwTotalBytesRead * 
                                       (dwFileSize - dwTotalBytesRead) / 1000);
        SetTimeLeft(dwSecondsLeft, dwTotalBytesRead, dwFileSize);
      }
    }
  }
}

void CALLBACK CFTPTransferDlg::_OnStatusCallBack(HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, 
                                                  LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
  //Convert from the SDK C world to the C++ world
  CFTPTransferDlg* pDlg = (CFTPTransferDlg*) dwContext;
  ASSERT(pDlg);
  ASSERT(pDlg->IsKindOf(RUNTIME_CLASS(CFTPTransferDlg)));
  pDlg->OnStatusCallBack(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
}

void CFTPTransferDlg::OnStatusCallBack(HINTERNET /*hInternet*/, DWORD dwInternetStatus, 
                                         LPVOID lpvStatusInformation, DWORD /*dwStatusInformationLength*/)
{
  switch (dwInternetStatus)
  {
    case INTERNET_STATUS_RESOLVING_NAME:
    {
      SetStatus(IDS_FTPTRANSFER_RESOLVING_NAME, (LPSTR) lpvStatusInformation);
      break;
    }
    case INTERNET_STATUS_NAME_RESOLVED:
    {
      SetStatus(IDS_FTPTRANSFER_RESOLVED_NAME, (LPSTR) lpvStatusInformation);
      break;
    }
    case INTERNET_STATUS_CONNECTING_TO_SERVER:
    {
      SetStatus(IDS_FTPTRANSFER_CONNECTING, (LPSTR) lpvStatusInformation);
      break;
    }
    case INTERNET_STATUS_CONNECTED_TO_SERVER:
    {
      SetStatus(IDS_FTPTRANSFER_CONNECTED, (LPSTR) lpvStatusInformation);
      break;
    }
    default:
    {
      break;
    }
  }
}

void CFTPTransferDlg::OnDestroy() 
{
  //Wait for the worker thread to exit
  if (m_pThread)
  {
    WaitForSingleObject(m_pThread->m_hThread, INFINITE);
    delete m_pThread;
    m_pThread = NULL;
  }

  //Free up the internet handles we may be using
  if (m_hFTPFile)
  {
    ::InternetCloseHandle(m_hFTPFile);
    m_hFTPFile = NULL;
  }

  if (!m_bUsingAttached)
  {
    if (m_hFTPConnection)
    {
      ::InternetCloseHandle(m_hFTPConnection);
      m_hFTPConnection = NULL;
    }
    if (m_hInternetSession)
    {
      ::InternetCloseHandle(m_hInternetSession);
      m_hInternetSession = NULL;
    }
  }
  m_hFTPConnection = NULL;
  m_hInternetSession = NULL;

  //Let the parent class do its thing
	CDialog::OnDestroy();
}

void CFTPTransferDlg::OnCancel() 
{
  //Just set the abort flag to TRUE and
  //disable the cancel button
  m_bAbort = TRUE;	
  GetDlgItem(IDCANCEL)->EnableWindow(FALSE);
  SetStatus(IDS_FTPTRANSFER_ABORTING_TRANSFER);
}

void CFTPTransferDlg::OnClose() 
{
  if (m_bSafeToClose)	
	  CDialog::OnClose();
  else
  {
    //Just set the abort flag to TRUE and
    //disable the cancel button
    m_bAbort = TRUE;	
    GetDlgItem(IDCANCEL)->EnableWindow(FALSE);
    SetStatus(IDS_FTPTRANSFER_ABORTING_TRANSFER);
  }
}
